﻿#pragma once

#include <memory>
#include <string>
#include <vector>
#include <mutex>
#include <map>



/**
*@brief 日志宏
* 
*@note 日志配置文件(以下为不进行配置时的默认值)

[Logger]
#日志级别阈值，小于此阈值级别的日志将不会写入(DEBUG INFO WARN ERROR 和 OFF)
Threshold=INFO
#日志目录和文件名设置，以下默认配置时日志将写入（其中模块名由SetLogConfig接口指定）： D:\Log\%Y-%m-%d\模块名.%Y-%m-%d.log
LogDir_W=D:/Log/
LogDir_L=./Log/
LogFileExt=.log
#日志保存周期（月）
LogKeepMonth=3

#是否将日志打印到控制台
PrintConsole=false
#二进制数据是否要用空格隔开
SplitBytes=true
#日志写入缓冲，启用此选项会加快日志写入速度，但是在意外关闭时可能导致日志丢失
LogCache=false
#以精简模式写日志（精简模式：级别+时间+文件名+函数名+日志内容 ）（完整模式：级别+时间+线程号+文件名+函数名+行号+日志内容）
SimpleLog=true
*/

//打印字符串日志，示例：FMT_INFO("hello,world:%d", 123);
#define FMT_DEBUG(_logFmt, ...)  ZL_PROJ_LOGGER->WriteLogFmt(__FILE__,__FUNCTION__,__LINE__,ZL_TLogLevl::eDEBUG,_logFmt, ## __VA_ARGS__)
#define FMT_INFO(_logFmt, ...)   ZL_PROJ_LOGGER->WriteLogFmt(__FILE__,__FUNCTION__,__LINE__,ZL_TLogLevl::eINFO,_logFmt, ## __VA_ARGS__)
#define FMT_WARN(_logFmt, ...)   ZL_PROJ_LOGGER->WriteLogFmt(__FILE__,__FUNCTION__,__LINE__,ZL_TLogLevl::eWARN,_logFmt, ## __VA_ARGS__)
#define FMT_ERROR(_logFmt, ...)  ZL_PROJ_LOGGER->WriteLogFmt(__FILE__,__FUNCTION__,__LINE__,ZL_TLogLevl::eERROR,_logFmt, ## __VA_ARGS__)

//打印二进制日志，示例：BYTE_INFO("用户数据：",  data, datalen);
#define BYTE_DEBUG(pszTips,pszBytes,nBytesLen) ZL_PROJ_LOGGER->WriteLogBytes(__FILE__,__FUNCTION__,__LINE__,ZL_TLogLevl::eDEBUG,pszTips,pszBytes,nBytesLen)
#define BYTE_INFO(pszTips,pszBytes,nBytesLen)  ZL_PROJ_LOGGER->WriteLogBytes(__FILE__,__FUNCTION__,__LINE__,ZL_TLogLevl::eINFO,pszTips,pszBytes,nBytesLen)
#define BYTE_WARN(pszTips,pszBytes,nBytesLen)  ZL_PROJ_LOGGER->WriteLogBytes(__FILE__,__FUNCTION__,__LINE__,ZL_TLogLevl::eWARN,pszTips,pszBytes,nBytesLen)
#define BYTE_ERROR(pszTips,pszBytes,nBytesLen) ZL_PROJ_LOGGER->WriteLogBytes(__FILE__,__FUNCTION__,__LINE__,ZL_TLogLevl::eERROR,pszTips,pszBytes,nBytesLen)

//你可以定义ZL_PROJ_LOGGER你自己的日志对象，否则默认使用单例对象
#ifndef ZL_PROJ_LOGGER
#define ZL_PROJ_LOGGER    ZL_TLogger::Default()
#endif

/**
*@brief      线程安全的ini配置文件读写类，一个对象读取一个配置文件
*            1.对注释有扩展支持(要求数据和注释不能在同一行)：
*                单行注释： 行首除去空格后以; //  #    <!--内容--> 开始的
*                多行注释： /*    内容    */
/**          2.如果传入的section为空：读写时会忽略掉配置文件中的带section的段(也就是只认为不带section的段才有效)
*            3.如果配置文件中存在相同的key：读时会以最后一个为准:写入时会写入所有相同key的值
*/
class ZL_TConfig
{
public:
    /**
    *@brief     构造配置文件对象
    *@param     p_pszPath[in]   配置文件路径
    *@return    
    */
    ZL_TConfig(const char* p_pszPath);

    /**
    *@brief     读配置文件
    *@param     p_pszSec[in]    [section]的值，如果没有section可以传入空
    *@param     p_pszKey[in]    键
    *@param     p_Default[in]   默认值，读取失败返回默认值
    *@return    值
    */
    std::string GetCfg(const char* p_pszSec, const char* p_pszKey, const char* p_pszDefault = "");
    int         GetCfg(const char* p_pszSec, const char* p_pszKey, int p_nDefault = 0);
    bool        GetCfg(const char* p_pszSec, const char* p_pszKey, bool p_bDefault = false);

    /**
    *@brief     写配置文件
    *@param     p_pszSec[in]      [section]的值，如果没有section可以传入空
    *@param     p_pszKey[in]      键
    *@param     p_Value[in]       值
    *@return    是否成功。
    */
    bool SetCfg(const char* p_pszSec, const char* p_pszKey, const char* p_pszValue);
    bool SetCfg(const char* p_pszSec, const char* p_pszKey, int p_nValue);
    bool SetCfg(const char* p_pszSec, const char* p_pszKey, bool p_bValue);

    /**
    *@brief     读取整个Section的所有键值对
    *@param     pszSec[in]       [section]的值，如果没有section可以传入空
    *@return    键值对，失败为空
    */
    std::map<std::string, std::string> GetAllConfigs(const char* pszSec);

    /**
    *@brief     读取Section名列表
    *@return    列表。失败为空
    */
    std::vector<std::string> GetAllSectionNames();

private:
    /**
    *@brief     读取配置文件并将其解析到内部的结构
    *@return    是否成功。
    */
    bool OpenAndParse();

    /**
    *@brief     将配置信息刷入到内部数据结构
    *@return    是否成功。
    */
    bool PushConfig(const char* pszSec, const char* pszKey, const char* pszValue);

    /**
    *@brief     将内部数据结构输出到文件
    *@return    是否成功。
    */
    bool FlushToFile();

private:
    bool m_bParsed;
    std::vector<std::shared_ptr<class TConfigSection > >  m_SectionList;
    std::string m_strFilePath;
    std::recursive_mutex m_CfgMutex;
};

//这些级别是从低到高的日志级别。设置为某个级别后，对应级别及其以上的级别会写入日志。
//比如在这里定义了INFO级别， 则INFO/WARN/ERROR级别的日志会写入文件。特殊的eOFF级别被设置后会彻底关闭日志。
enum class ZL_TLogLevl
{
    eDEBUG = 0,
    eINFO,
    eWARN,
    eERROR,
    eOFF
};

/**
*@brief 日志类
*/
class ZL_TLogger
{
public:
    explicit ZL_TLogger(size_t buffsize = 1024 * 1024);
    ~ZL_TLogger();

    /**
    *@brief 开箱即用的默认日志对象指针，你也可以自己创建其他日志对象
    *        警告！！默认的日志对象指针第一行日志是非线程安全的，后续的所有日志是线程安全的
    *        所以，一般的做法是在main函数第一行调用一次ZL_TLogger::Default()->SetLogConfig()，后面的应用逻辑创建线程后在多线程环境下写的日志才线程安全
    */
    static ZL_TLogger* Default();

    /**
    *@brief     设置日志配置
    *@param     p_szModuleName    [in]    模块名，也是日志的文件名，多个模块都要写日志时通过此名称区分
    *@param     p_szFilePath      [in]    配置文件路径
    *@param     p_szSection       [in]    配置文件中属于日志配置的节
    *@return    是否成功。失败时使用默认配置
    */
    bool SetLogConfig(const char* p_szModuleName, const char* p_szFilePath, const char* p_szSection = "Logger");

    /**
    *@brief     执行日志维护任务，你可以手动调用，或者每次跨天会自动执行
    *@return    
    */
    void ExecuteLogMaintenance();

    /**
    *@brief     写日志，日志写完毕后添加换行符，单条日志长度有限制
    *@param     p_pFileName    [in]    当条日志的文件名称，可为空
    *@param     p_pFuncName    [in]    当条日志的函数名称，可为空
    *@param     p_ulLine       [in]    当条日志在文件中的行数
    *@param     p_nLevel       [in]    当条日志的日志等级
    *@param     p_pszFmt       [in]    日志内容格式串/日志内容
    *@return    
    */
    void WriteLogFmt(const char* p_pFileName, const char* p_pFuncName, unsigned int p_uLine, ZL_TLogLevl p_nLevel, const char* p_pszFmt, ...);

    /**
    *@brief     写二进制数据日志，日志写完毕后添加换行符
    *           二进制日志没有长度限制，所以如果要写巨大数据，请用这个接口。
    *@param     p_pFileName    [in]    当条日志的文件名称，可为空
    *@param     p_pFuncName    [in]    当条日志的函数名称，可为空
    *@param     p_ulLine       [in]    当条日志在文件中的行数
    *@param     p_nLevel       [in]    当条日志的日志等级
    *@param     p_pszTips      [in]    前导提示字符串
    *@param     p_pszBytes     [in]    数据日志内容
    *@param     p_nBytesLen    [in]    数据日志内容长度
    *@return    
    */
    void WriteLogBytes(const char* p_pFileName, const char* p_pFuncName, unsigned int p_uLine, ZL_TLogLevl p_nLevel, const char* p_pszTips, const void* p_pszBytes, size_t p_nBytesLen);

private:

    void UpdateConfig();
    bool CheckLogger(struct tm& tmValue);
    void CleanHistoryLog();

private:
    //日志配置
    ZL_TLogLevl     m_eThreshold;
    std::string     m_strLogDir;
    std::string     m_strLogFileName;
    std::string     m_strLogFileExt;
    int             m_nLogKeepMonth;
    bool            m_bPrintConsole;
    bool            m_bSplitBytes;
    bool            m_bLogCache;
    bool            m_bSimpleLog;

    //内部结构
    std::recursive_mutex    m_Mutex;
    FILE*           m_pLogFile;
    char*           m_pFmtBuff;
    size_t          m_fmtBufSize;

    //当前文件的年月日
    struct tm       m_tmCurrent;
};


